/**
 * LocalTransactionManager. This is the manager of the local transaction. Each server needs a single one
 * 
 * @author guillaume
 */
package gicom.transaction;

import gicom.GenericManager;
import gicom.GenericServer;
import gicom.PersistentObject;
import gicom.generated.bankServices.Account;
import gicom.generated.bankServices.Branch;
import gicom.generated.bankServices.BranchHelper;
import gicom.generated.bankServices.ProblemException;
import gicom.generated.transaction.GlobalTransactionManager;
import gicom.generated.transaction.GlobalTransactionManagerHelper;
import gicom.generated.transaction.InvalidStampException;
import gicom.generated.transaction.InvalidStateException;
import gicom.generated.transaction.LocalTransactionManager;
import gicom.generated.transaction.LocalTransactionManagerHelper;
import gicom.generated.transaction.LocalTransactionManagerOperations;
import gicom.generated.transaction.LocalTransactionManagerPOATie;
import gicom.generated.transaction.State;
import gicom.generated.transaction.Vote;
import gicom.transaction.AccountAction.OperationType;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import org.omg.PortableServer.Servant;

public class LocalTransactionManagerImpl extends PersistentObject implements LocalTransactionManagerOperations {

    /**
     * transactions map. Key is the transaction id, Object is a list of
     * AccountAction, as more than one operation maybe be done for the same
     * transaction on the same Branch
     */
    private transient Map<Integer, List<AccountAction>> transactions;
    /**
     * The global transaction manager
     */
    private GlobalTransactionManager gtm;
    /**
     * Branch on which the LTM is set
     */
    private Branch branch;
    /**
     * Logger of the object
     */

    // TODO : use looger
    private static Logger logger = Logger.getLogger(LocalTransactionManagerImpl.class.getName());
    private static LocalTransactionManagerImpl localTM;

    // public static void init(GlobalTransactionManager gtm, Branch branch, POA
    // poa, ORB orb) {
    // localTM = new LocalTransactionManagerImpl(gtm, branch);
    // }

    public static LocalTransactionManagerImpl getLTM() {
	return localTM;
    }

    /**
     * Constructor of a LocalTransactionManager
     * 
     * @param gtm
     *                the associated GTM
     * @param branch
     *                the branch on which the LTM will work
     */
    public LocalTransactionManagerImpl(Branch branch) {
	super("LocalTM_" + branch.name());
	this.branch = branch;
	transactions = new HashMap<Integer, List<AccountAction>>();
	/*
	 * try { persistentpoa = poa.find_POA("persistentpoa", true); } catch
	 * (AdapterNonExistent e) { // TODO Auto-generated catch block
	 * e.printStackTrace(); }
	 */
    }

    public LocalTransactionManagerImpl() {

    }

    /**
     * Prepare the transaction : first phase of the two
     * 
     * @param id
     *                id of the transaction to prepare
     */
    @Override
    public Vote prepare(int id) throws InvalidStampException, InvalidStateException {
	logger.info("LocalTM PREPARING ...");
	Vote vote = Vote.VOTECOMMIT;
	// if the transaction exists
	if (this.transactions.containsKey(id)) {
	    Iterator<AccountAction> iter = this.transactions.get(id).iterator();
	    // for all the account enroll in the transation, ask them to be
	    // prepared
	    while (iter.hasNext()) {
		AccountAction accountAction = (AccountAction) iter.next();
		if (accountAction.getState().equals(State.INITIAL)) {
		    accountAction.setState(State.PREPARE);
		    saveTmpState(accountAction, id);
		    vote = accountAction.prepare(id);
		    // if the vote is a rollback
		    if (vote.equals(Vote.VOTEROLLBACK)) {
			System.out.println("vote rollback");
			return vote;
		    } else {
			System.out.println("vote commit : " + vote);
		    }
		}
	    }

	} else {
	    throw new InvalidStampException();
	}

	return vote;
    }

    /**
     * Commit the transaction : Second phase of the transaction
     * 
     * @param id
     *                id of the transaction to commit
     */
    @Override
    public void commit(int id) throws InvalidStampException, InvalidStateException {
	logger.info("LocalTM COMMITING ...");
	if (this.transactions.containsKey(id)) {
	    Iterator<AccountAction> iter = this.transactions.get(id).iterator();
	    while (iter.hasNext()) {
		AccountAction accountAction = (AccountAction) iter.next();
		if (accountAction.getState().equals(State.READY)) {
		    if (accountAction.action(id)) {
			validateTmpState(accountAction, id);
			deleteTmpState(accountAction, id);
			accountAction.setState(State.COMMITED);
		    } else {
			this.gtm.rollback(id);
			accountAction.setState(State.ROLLBACKED);
		    }
		}
		// TODO : else
	    }
	} else {
	    throw new InvalidStampException();
	}

    }

    /**
     * Rollback the transaction : Second phase of the transaction
     * 
     * @param id
     *                id of the transaction to rollback
     */
    @Override
    public void rollback(int id) throws InvalidStampException, InvalidStateException {
	logger.info("LocalTM ROLLBACKING ...");
	if (this.transactions.containsKey(id)) {
	    Iterator<AccountAction> iter = this.transactions.get(id).iterator();
	    while (iter.hasNext()) {
		AccountAction accountAction = (AccountAction) iter.next();
		if (accountAction.getState().equals(State.READY)) {
		    accountAction.setState(State.ABORT);
		    accountAction.rollback(id);
		    deleteTmpState(accountAction, id);
		    accountAction.setState(State.ROLLBACKED);
		}
		// TODO : else
	    }
	} else {
	    throw new InvalidStampException();
	}

    }

    /**
     * Retail an account
     * 
     * @param id
     *                Transaction stamp
     * @param d
     *                How much to retail
     * @param client
     *                Client to retail
     * @param accountNumber
     *                Account number to retail on
     */
    public void debit(int id, float d, String client, String accountNumber) {
	logger.info("LocalTM DeBiT ...");
	try {
	    Account account = this.branch.findCustomer(client).findAccountByPrimaryKey(accountNumber);
	    if (!this.transactions.containsKey(id)) {
		this.transactions.put(id, new ArrayList<AccountAction>());
	    }

	    logger.info("account selectionné : " + account.number());

	    this.transactions.get(id).add(new AccountAction(OperationType.DEBIT, account, d, id));

	    gtm.enroll(id, (LocalTransactionManager) LocalTransactionManagerHelper.narrow(getReference()));

	} catch (ProblemException e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    logger.log(Level.WARNING, "Account not found : " + accountNumber);

	} catch (InvalidStampException e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    logger.log(Level.WARNING, "Transaction not initialized in the GTM : " + id);
	} catch (InvalidStateException e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    logger.log(Level.WARNING, "Transaction " + id + "is not in the right state");
	} catch (ObjectNotActive e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (WrongPolicy e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (Exception ex) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    Logger.getLogger(LocalTransactionManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
	}

    }

    /**
     * Credit an account
     * 
     * @param id
     *                Transaction stamp
     * @param c
     *                How much to credit
     * @param client
     *                Client to credit
     * @param accountNumber
     *                Account number to credit on
     */
    public void credit(int id, float c, String client, String accountNumber) {
	logger.info("LocalTM CrEdIt ...");
	try {
	    Account account = this.branch.findCustomer(client).findAccountByPrimaryKey(accountNumber);
	    if (!this.transactions.containsKey(id)) {
		this.transactions.put(id, new ArrayList<AccountAction>());
	    }

	    this.transactions.get(id).add(new AccountAction(OperationType.CREDIT, account, c, id));

	    gtm.enroll(id, (LocalTransactionManager) LocalTransactionManagerHelper.narrow(getReference()));

	} catch (ProblemException e) {
	    try {
		gtm.rollback(id);
	    } catch (InvalidStampException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    } catch (InvalidStateException e1) {
		// TODO Auto-generated catch block
		e1.printStackTrace();
	    }
	    logger.log(Level.WARNING, "Account not found : " + accountNumber);
	} catch (InvalidStampException e) {
	    logger.log(Level.WARNING, "Transaction not initialized in the GTM : " + id);
	} catch (InvalidStateException e) {
	    logger.log(Level.WARNING, "Transaction " + id + "is not in the right state");
	} catch (ObjectNotActive e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (WrongPolicy e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	} catch (Exception ex) {
	    Logger.getLogger(LocalTransactionManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
	}
    }

    /**
     * Save the temporary state of an account
     * 
     * @param accountAction
     *                AccountAction that manages the particant
     * @param id
     *                id of the transaction
     */
    private void saveTmpState(AccountAction accountAction, int id) {

	logger.info("LocalTM SAVING ...");

	// PersistentObjectManager pom =
	// PersistentObjectManagerImpl.getManager();

	Account acc = accountAction.getAccount();

	String name = acc.number() + "___" + id;

	acc.saveTmp(name);

    }

    /**
     * Validate the temporary state of an account
     * 
     * @param accountAction
     *                AccountAction that manages the particant
     * @param id
     *                id of the transaction
     */
    private static void validateTmpState(AccountAction accountAction, int id) {

	logger.info("LocalTM VALIDATING ...");

	GenericManager pom = GenericServer.defaultGenericServer.getGenericManager();

	Account acc = accountAction.getAccount();

	String name = acc.number() + "___" + id;

	// pom.rename(name.getBytes(), acc.number().getBytes());
    }

    /**
     * Delete the temporary state of an account
     * 
     * @param accountAction
     *                AccountAction that manages the particant
     * @param id
     *                id of the transaction
     */
    private static void deleteTmpState(AccountAction accountAction, int id) {

	logger.info("LocalTM DELETING ...");

	GenericManager pom = GenericServer.defaultGenericServer.getGenericManager();

	Account acc = accountAction.getAccount();

	String name = acc.number() + "___" + id;

	try {
	    Thread.sleep(10000);
	} catch (InterruptedException e) {
	    // TODO Auto-generated catch block
	    e.printStackTrace();
	}

	// pom.remove(name.getBytes());
    }

    @Override
    public Servant createServant() {
	return new LocalTransactionManagerPOATie(this);
    }

    @Override
    public GlobalTransactionManager globalTM() {
	return this.gtm;
    }

    /*
     * Persistence
     */
    private transient String refGTM;
    private transient String refBranch;

    @Override
    public void doReload(ORB orb, POA poa) throws Exception {
	this.branch = BranchHelper.narrow(orb.string_to_object(this.refBranch));
	this.gtm = GlobalTransactionManagerHelper.narrow(orb.string_to_object(this.refGTM));
    }

    @Override
    protected void doPostCreate(ORB orb, POA poa) throws Exception {
	NamingContextExt naming = NamingContextExtHelper.narrow(orb.resolve_initial_references("NameService"));
	this.gtm = GlobalTransactionManagerHelper.narrow(naming.resolve(naming.to_name("GlobalTM")));
	this.save();
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

	// GTM
	this.refGTM = in.readUTF();

	// branch
	this.refBranch = in.readUTF();

	// id
	this.id = in.readUTF();

	this.transactions = new HashMap<Integer, List<AccountAction>>();

	// transactions
	int i = in.read();
	for (int j = 0; j < i; j++) {
	    List<AccountAction> lAcc = new ArrayList<AccountAction>();
	    int t = in.readInt();
	    int idTmp = 0;
	    for (int k = 0; k < t; k++) {
		AccountAction a = (AccountAction) in.readObject();
		lAcc.add(a);
		idTmp = a.getId();
	    }
	    this.transactions.put(idTmp, lAcc);
	}

    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
	try {

	    ORB orb = getORB();

	    out.writeUTF(orb.object_to_string(this.gtm));

	    // branch
	    out.writeUTF(orb.object_to_string(this.branch));

	    // id
	    out.writeUTF(this.id);

	    // transactions
	    out.writeInt(this.transactions.size());
	    for (List<AccountAction> accAct : this.transactions.values()) {
		// out.writeInt(accAct.size());
		for (AccountAction aA : accAct) {
		    out.writeObject(aA);
		}
	    }
	} catch (Exception ex) {
	    Logger.getLogger(LocalTransactionManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
	}

    }

    @Override
    public void desactivate() {
	try {
	    getPOA().deactivate_object(id.getBytes());
	} catch (Exception ex) {
	    Logger.getLogger(LocalTransactionManagerImpl.class.getName()).log(Level.SEVERE, null, ex);
	}
    }
}
